﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Collections;

namespace _12306Bot.AuthCode
{
    class GetAuthCode
    {
        private static int m_libIndex = 0;

        public static String get(Bitmap bmAuthCode)
        {
            //String strAuthCode;

            ////验证码的特征码列表
            //AuthCharModule[] acmList;
            ////页面验证码
            //AuthCharModule acmMain;

            //int[,] picColor = GetPicColorArrayPixels(bmAuthCode);
            ////将图片转换成模块
            //acmMain = new AuthCharModule(picColor);
            ////获取全部验证码示例
            //acmList = HandleAuthChar.getAuthCharModuleArr();


            //List<MatchedTagCode> mtcList = getMatchedCode(acmMain, acmList);

            //strAuthCode = getAuthCodeSEQ(mtcList);

            return getAuthCodeTemp(bmAuthCode);
            //return strAuthCode;
        }

        [System.Runtime.InteropServices.DllImport("recognize.dll")]
        public static extern bool GetCodeFromBuffer(int LibFileIndex, byte[] FileBuffer, int ImgBufLen, StringBuilder Code);
        [System.Runtime.InteropServices.DllImport("recognize.dll")]
        public static extern int LoadLibFromFile(string LibFilePath, string nSecret);

        public static String getAuthCodeTemp(Bitmap bmp)
        {
            string startupPath = System.Windows.Forms.Application.StartupPath;
            if (m_libIndex == 0)
            {
                m_libIndex = LoadLibFromFile(startupPath + @"\12306.lib", "123");
            }
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
            int length = (int)stream.Length;
            byte[] fileBuffer = new byte[length];
            fileBuffer = stream.ToArray();
            stream.Close();
            bool flag = false;
            StringBuilder code = new StringBuilder(0, 0x100);

            flag = GetCodeFromBuffer(m_libIndex, fileBuffer, length, code);
            if (flag)
            {
                return code.ToString();
            }
            else
            {
                return "";
            }
        }

        /// <summary>
        /// 用特征码去匹配验证码的整个区域
        /// <para>特征码的前景的像素点，除去超出被截的部分，如果完全落在验证码的前景像素点的范围内，则算匹配成功，否则算失败</para>
        /// </summary>
        /// <param name="acmMain">验证码</param>
        /// <param name="acmList">特征码</param>
        /// <returns>是否匹配成功</returns>
        private static List<MatchedTagCode> getMatchedCode(AuthCharModule acmMain, AuthCharModule[] acmList)
        {
            //返回值
            List<MatchedTagCode> matchedCodeList = new List<MatchedTagCode>();
            ////用特征码去匹配验证码
            ////基本原理是让特征码去和验证码重叠，对重叠的部分进行异或
            ////循环的过程是验证码不动，特征码相对验证码进行移动
            ////第一层循环
            ////遍历所有特征码
            //for (int i = 0; i < acmList.Length; i++)
            //{
            //    //第二层循环
            //    //起：用特征码的下半部分像素去和验证码进行向上对齐的匹配
            //    //止：用特征码的上半部分像素去和验证码进行向下对齐的匹配
            //    //本层循环目的是让特征码自上而下地移动
            //    //本层的循环变量标明，特征码与验证码重叠时，特征码最中间的那行匹配验证码的第几行
            //    //由于验证码的宽度理论上大于特征码，所以只需循环验证码宽度的次数就可以了
            //    for (int rowCur = 0; rowCur <= acmMain.RowCount - 1; rowCur++)
            //    {
            //        //特征码移位数（依图片的概念而言的上移，数学上是左移，反之是右移）
            //        //移动量为特征码尾行到验证码尾行之间的间隔
            //        //特征码尾行为，特征码中间行到特征码尾行之间的行数加上特征码中间行的移动行数
            //        int shiftDigit = acmMain.RowCount - ((int)(acmList[i].RowCount / 2) + rowCur);

            //        //第三层循环
            //        //起：用特征码的右半部分像素去和验证码进行向左对齐的匹配
            //        //止：用特征码的左半部分像素去和验证码进行向右对齐的匹配
            //        //本层循环是让特征码自左向右地移动
            //        //本层的循环变量标明，特征码与验证码重叠时，特征码最中间的那列匹配验证码的第几列
            //        for (int colCur = 0; colCur <= acmMain.ColCount; colCur++)
            //        {
            //            //特征码左起点，用验证码的横坐标来表示
            //            //左起点从中列开始，依起点向左移动
            //            int colStart = colCur - (int)(acmList[i].ColCount / 2);
            //            if (colStart < 0)
            //            {
            //                colStart = 0;
            //            }
            //            //特征码右终点，用验证码的横坐标来表示
            //            //注意按以下算法得到的colStop作为终点，在参与之后的循环时，边界是小于，而不是小于等于
            //            //加1减1修正：列数向后数的数目和列数的奇偶有关
            //            int colStop = (int)((acmList[i].ColCount + 1) / 2) + colCur;
            //            if (colStop > acmMain.ColCount)
            //            {
            //                colStop = acmMain.ColCount;
            //            }

            //            //第四层循环
            //            //注意，起止值都是验证码的横坐标值
            //            //自左向右扫描特征码并匹配
            //            //循环变量，这个变量放在外面定义，目的是循环结束后需要用这个变量来进行计算
            //            int colIdx;
            //            //特征码的命中像素个数
            //            int hitPixCount = 0;
            //            //计算特征码的空列数
            //            //特征码被截取后，剩余的部分可能会有空白，如果这段空白出现在前面，则在计算起始位置的时候会出现问题
            //            //比如，某验证码上字母被截取了底部，该字母宽度为10，截取后宽度只剩下8了。
            //            //在验证码上看到的，该字母可能是从第11列开始的，但是在进行匹配的时候，colStart可能就是从11-2=9列开始的
            //            //所以在计算真正的起始列时，要加上一个空白的修补数
            //            int colBlank = 0;
            //            for (colIdx = colStart; colIdx < colStop; colIdx++)
            //            {
            //                //正式开始匹配
            //                //匹配最终结果
            //                uint colResult;
            //                //设置验证码的起始列
            //                int listColStart = (int)(acmList[i].ColCount / 2) - colCur > 0 ? (int)(acmList[i].ColCount / 2) - colCur : 0;
            //                //大于0左移，小于0右移
            //                if (shiftDigit > 0)
            //                {
            //                    colResult = acmList[i].AuthCodeColBit[listColStart + colIdx - colStart] << shiftDigit;
            //                }
            //                else
            //                {
            //                    colResult = acmList[i].AuthCodeColBit[listColStart + colIdx - colStart] >> (shiftDigit * -1);
            //                }
            //                //截位
            //                //左移之后，特征码高位超出范围的部分可能存在1，下面的操作是把这些1都变成0
            //                colResult &= (uint)Math.Pow(2, acmMain.RowCount) - 1;
            //                //对特征码的暴露部分的像素个数进行计算，如果未命中，则此计算无效
            //                hitPixCount += countOneInBinary(colResult);
            //                //解释见colBlank的注释
            //                if (hitPixCount == 0)
            //                {
            //                    colBlank++;
            //                }
            //                //与后异或
            //                //这个算法的原理阐述起来很麻烦，想想就想明白了
            //                colResult = (colResult & acmMain.AuthCodeColBit[colIdx]) ^ colResult;
            //                //只要不匹配就跳出本循环
            //                if (colResult != 0)
            //                {
            //                    break;
            //                }
            //            }
            //            //由于一旦不比配就跳出循环，所以正常终了的循环就是全匹配的
            //            //两个条件，第一用于判断是否正常跳出循环，第二个条件是看特征码是不是都是空白，从而完成了匹配。
            //            if (colIdx == colStop && hitPixCount != 0)
            //            {
            //                //当前参与匹配的特征码的宽度
            //                int matchPixWidth = colStop - colStart + 1;
            //                //当前参与匹配的特征码的高度
            //                int matchPixHeight;
            //                if (shiftDigit > 0)
            //                {
            //                    if (acmList[i].RowCount + shiftDigit < acmMain.RowCount)
            //                    {
            //                        matchPixHeight = acmList[i].RowCount;
            //                    }
            //                    else
            //                    {
            //                        matchPixHeight = acmMain.RowCount - shiftDigit;
            //                    }
            //                }
            //                else
            //                {
            //                    matchPixHeight = acmList[i].RowCount + shiftDigit;
            //                }
            //                //= shiftDigit > 0 ? acmList[i].RowCount + shiftDigit - acmMain.RowCount : acmList[i].RowCount + shiftDigit;
            //                //MatchedTagCode matchedCode =  new MatchedTagCode(acmList[i].AuthChar, colStart, (float)(matchPixWidth * matchPixHeight) / (float)(acmList[i].RowCount * acmList[i].ColCount), acmList[i].PixCount);
            //                MatchedTagCode matchedCode = new MatchedTagCode(acmList[i].AuthChar, colStart + colBlank, (float)hitPixCount / acmList[i].PixCount, acmList[i].PixCount);
            //                matchedCodeList.Add(matchedCode);
            //                //acmList[i].setMatchRate(matchPixWidth * matchPixHeight);
            //                //acmList[i].DisplayPosition.Add(colStart);
            //            }
            //        }
            //    }
            //}
            return matchedCodeList;
        }

        /// <summary>
        /// 计算整数转换为二进制后，里面1的个数
        /// 算法逻辑见：http://saturnman.blog.163.com/blog/static/557611201010944228627/
        /// </summary>
        /// <param name="binCode">输入的整数</param>
        /// <returns>1的个数</returns>
        private static int countOneInBinary(uint binCode)
        {
            int cnt;
            for (cnt = 0; cnt < binCode; binCode &= binCode - 1)
            {
                cnt++;
            }
            return cnt;
        }

        /// <summary>
        /// 根据已经设定好的命中位置，返回验证码字符串
        /// </summary>
        /// <param name="mtcList">设定好了命中位置的特征码</param>
        /// <returns>验证码字符串</returns>
        private static String getAuthCodeSEQ(List<MatchedTagCode> mtcList)
        {
            StringBuilder AuthCodeSEQ = new StringBuilder();
            mtcList.Sort();
            SortedList hitCode = new SortedList();
            //下面这段循环的算法是这样的
            //默认，命中像素比率最高的4个特征码为识别后的字符，如果这四个特征码的命中位置间隔很近（命中位置理应是唯一的）
            //则比较特征码的像素数量，数量多的最终匹配成功
            for (int i = 0; i < 4; i++)
            {
                //如果命中的特征码小于4，则说明有问题了，直接返回空
                if (mtcList.Count < 4) return "";

                hitCode.Add(mtcList[i].DisplayPosition, mtcList[i]);
                for (int j = i + 1; j < mtcList.Count; j++)
                {
                    if (Math.Abs(mtcList[i].DisplayPosition - mtcList[j].DisplayPosition) < HandleAuthChar.colIntervalRange)
                    {
                        if (((MatchedTagCode)hitCode[mtcList[i].DisplayPosition]).PixCount < mtcList[j].PixCount)
                        {
                            hitCode[mtcList[i].DisplayPosition] = mtcList[j];
                        }
                        mtcList.RemoveAt(j);
                        j--;
                    }
                }
                //for (int j = 0; j < mctList[i].DisplayPosition.Count; j++)
                //{
                //    //如果有几个特征码在同一坐标上匹配成功，则认为前景像素最多的那个特征码最终匹配成功
                //    if (hitCode.ContainsKey(mctList[i].DisplayPosition[j]))
                //    {
                //        //判断现在这个特征码的像素数和之前保存的像素数哪个多
                //        if (((AuthCharModule)hitCode[mctList[i].DisplayPosition[j]]).PixCount < mctList[i].PixCount)
                //        {
                //            hitCode[mctList[i].DisplayPosition[j]] = mctList[i];
                //        }
                //    }
                //    else
                //    {
                //        //Add的后一个参数之所以不仅仅扔一个特征码的字符进去，是为了本if语句的前半部分处理方便
                //        hitCode.Add(mctList[i].DisplayPosition[j], mctList[i]);
                //    }
                //}
            }
            foreach (DictionaryEntry auth in hitCode)
            {
                AuthCodeSEQ.Append(((MatchedTagCode)auth.Value).AuthChar);
            }
            return AuthCodeSEQ.ToString();
        }

        /// 根据RGB，计算灰度值
        /// </summary>
        /// <param name="posClr">Color值</param>
        /// <returns>灰度值，整型</returns>
        private static int GetGrayNumColor(Color posClr)
        {
            return Convert.ToInt32((double)(((0.33 * posClr.R) + (0.34 * posClr.G)) + (0.33 * posClr.B)));
        }

        /// <summary>
        /// 将图片转换成二值数组，不做任何精简。
        /// 字符移出验证码显示范围后，可能会出现断掉的现象，这时如果精简，则会让验证码变形。
        /// </summary>
        /// <param name="bmObj">图片</param>
        /// <returns>像素数组</returns>
        private static int[,] GetPicColorArrayPixels(Bitmap bmObj)
        {
            //返回用数组
            //图片的像素的坐标表示为（高坐标值，宽坐标值）
            //宽坐标值代表列编号，高坐标值代表行编号
            int[,] PicColorArray = new int[bmObj.Height, bmObj.Width];
            for (int i = 0; i < bmObj.Height; i++)
            {
                for (int j = 0; j < bmObj.Width; j++)
                {
                    if (HandleAuthChar.GrayLine < GetGrayNumColor(bmObj.GetPixel(j, i)))
                    {
                        PicColorArray[i, j] = HandleAuthChar.binPicCode.CodeOfGroud;
                    }
                    else
                    {
                        PicColorArray[i, j] = HandleAuthChar.binPicCode.CodeOfPic;
                    }
                }
            }
            return PicColorArray;
        }

        /// <summary>
        /// 获取匹配后错误像素个数最少的字符
        /// </summary>
        /// <param name="acmList">传入的数组列表</param>
        /// <returns>最匹配的字符</returns>
        private static char getLeastMissPixChar(AuthCharModule[] acm)
        {
            //匹配后错误最少的字符的序号
            int leastMissPixCharIdx = 0;
            //匹配后错误最少的字符的错误次数
            int leastMissPixCharCount = 10000;
            for (int i = 0; i < acm.Length; i++)
            {
                if (leastMissPixCharCount > acm[i].MissPixCount)
                {
                    leastMissPixCharCount = acm[i].MissPixCount;
                    leastMissPixCharIdx = i;
                }
            }
            return acm[leastMissPixCharIdx].AuthChar;
        }

        /// <summary>
        /// 测试用，根据数组返回图片
        /// </summary>
        /// <param name="PicColorArray"></param>
        /// <returns></returns>
        private static Bitmap GenPicFromArr(int[,] PicColorArray)
        {
            Bitmap bmRtn = new Bitmap(PicColorArray.GetLength(1), PicColorArray.GetLength(0));
            for (int i = 0; i < PicColorArray.GetLength(0); i++)
            {
                for (int j = 0; j < PicColorArray.GetLength(1); j++)
                {
                    if (PicColorArray[i, j] == 0)
                    {
                        PicColorArray[i, j] = 255;
                    }
                    else
                    {
                        PicColorArray[i, j] = 0;
                    }
                    bmRtn.SetPixel(j, i, Color.FromArgb(PicColorArray[i, j], PicColorArray[i, j], PicColorArray[i, j]));
                }
            }
            return bmRtn;
        }
    }
}
